
package todoitreminder;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Logger;
import java.util.Collections;

import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;

import com.google.wave.api.*;



@SuppressWarnings("serial")
public class Todoit_reminderServlet extends AbstractRobotServlet {
	///private static final long serialVersionUID = -1L;

	private static final Logger log = Logger.getLogger(
			Todoit_reminderServlet.class.getName());
	
	// Exact URL address of the gadget that the robot wants.
	private static final String TODOIT_GADGET_URL = 
		"http://todoit-wave.googlecode.com/svn/trunk/todoit.xml";

	// Name of annotations that represent "add me as task".
	private static final String ANNOTATION_ADD_AS_TASK = 
		"todoit.wavebot.appspot.com/addastask";
	
	// Request text that users post to set their "default wave".
	private static final String TEXT_USERREQ_SET_DEFAULT_WAVE = 
		"#default-list"; // "#todoit-set-default-wave";
	
	private static final String ID_BUTT_SET_DEFAULT_WAVE =
		"todoit-set-default-wave";

	/**
	 * TEMP <user, defaultWaveID> DATABASE TO OVERCOME UNAVAILABLE CONTEXT:
	 * Actually, should store waveId+waveletId+blipId+gadgetId of gadget in DB,
	 * - WHEN the Wave API adds support to handle the below NOTE.
	 * __From the Wave API FAQ__:  http://wave-api-faq.appspot.com/#allcontext  "In our next revision of the API, we will let developers specify that they want "all" the context"...

	 * __NOTE__: it is currently not supported to get a Wavelet's metadata by ID !!!!!!!
	 * This happens: http://code.google.com/p/google-wave-resources/issues/detail?id=181
	 * - because the metadata is not ready for the robot, and no 'waitForMetadata()'...

	 * So we need to also save a Map<waveID, GadgetInstance> with LIVE gadget instances -
	 * - which is unlikely to remain available in memory... making this very hacky...
	 * EDIT!!!!!!!!!!: crap. Even when working this way we apparently don't get state updates and can't update state via a saved GadgetInstance from the Map (it's metadata context "expires", NOT EVEN saving references, IDs, contexts, handles  to blips/wavelets helps this).
	 * Different approach: save tasks in memory until we get a gadget event with valid context.
	 * UPDATED to use AppEngine's memcached AND the datastore PersistenceManager DB.
	 */
	//Map<String,String> users_waveid = new HashMap<String, String>();
	//Map<String,Gadget> waveid_gadgetinstance = new HashMap<String, Gadget>();
	//Map<String,Blip> waveid_blipinstance = new HashMap<String, Blip>();
	// this too, for bug: http://development.lombardi.com/?p=882
	//Map<String,TextView> waveid_gadgettextview = new HashMap<String, TextView>();

	// different approach: save tasks in memory until we get a gadget event with valid context
	// UPDATED to use AppEngine's memcached AND the datastore PersistenceManager DB.
    Cache cache;
    
	//Map<String,List<String>> waveid_listoftaskstoadd = new HashMap<String, List<String>>();
	// save changes to task titles in task waves that need to be propagated to gadget waveid
	//Map<String,List<Tuple<String>>> waveid_listoftaskidtitle = new HashMap<String, List<Tuple<String>>>();


	// Message text for notifying users about the context state of the Wave API.
	private static final String TEXT_CONTEXT_LOST_MSG = 
		"-- Now we have to wait for a new event from the gadget, " +
		"before the robot can update it. Any event generated by any " +
		"user is good for this purpose. (The next time someone plays with the " +
		"gadget, the robot will see and update it). -- This is mandatory because" +
		" the context to the gadget is lost until the robot sees a new event" +
		" related to it. (Can be fixed when the Wave API creates support for" +
		" automatically re-obtaining this context). --";
	
	// URL address prefix of link to a wave by its ID.
	private static final String TEXT_LINK_TO_WAVEID_SB = 
		"https://wave.google.com/a/wavesandbox.com/#restored:wave:";
	
	
	
	public Todoit_reminderServlet() {
		super();
		
	    try {
	        CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
	        cache = cacheFactory.createCache(Collections.emptyMap());
	        log.warning("Got the cache factory (size: " + cache.size() + ").");
	    } catch (CacheException e) {
	        log.severe("Can't get the cache factory.");
	        cache = null;
	    }
	}

	@Override
	public void processEvents(RobotMessageBundle bundle) { // Wave OEP
		if (bundle.wasSelfAdded()) {
			processWasSelfAdded(bundle);
		}
		
	    for (Event e: bundle.getEvents()) {
	    	if (e.getType() == EventType.WAVELET_PARTICIPANTS_CHANGED) {
	    		processParticipantsChanged(bundle, e);
	    	} else if (e.getType() == EventType.FORM_BUTTON_CLICKED) {
	    		processFormButtonClicked(e);
	    	} else if (e.getType() == EventType.DOCUMENT_CHANGED) {
	    		// should be  BLIP_SUBMITTED  (if updated at edit 'Done')
	    		processDocumentChanged(bundle, e);
	    	} else if (e.getType() == EventType.BLIP_SUBMITTED) {
	    		processBlipSubmitted(bundle, e);
	    		//processDocumentChanged(bundle, e);
	    	}
		}
	}
	
	public void processWasSelfAdded(RobotMessageBundle bundle) {
		Wavelet wavelet = bundle.getWavelet();

	  	StringBuilder sp = new StringBuilder();
	  	// FIXME: version.
		sp.append("\nHello from the ToDoIt Robot!"
				+ "\nRead about it: http://code.google.com/p/todoit-wave/ \n"
				//+ " This wave's creator is: [" 
				//+ wavelet.getCreator() + "]\n"
				);
	
	    Blip blip = wavelet.appendBlip();
	    TextView textView = blip.getDocument();

	    // DEMO: only for developers
	    if (debugIsDeveloper(wavelet.getCreator())) {

	    	// Save Wavelet.rootBlip.Gadget(URL) as DEFAULT WAVE for users:
			String waveID = wavelet.getWaveId();
			Blip rootBlip = wavelet.getRootBlip();

			// find the Gadget
			Gadget gadget = null;
			TextView rtextView = rootBlip.getDocument();
			GadgetView rgadgetView = null;
			
			try {
				rgadgetView = rtextView.getGadgetView();
				gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
			}
			catch (Exception e) {
				log.warning("getGadget/s(URL) exception instead of NULL? [" +
						e.getMessage()+ "]");
			}
			
			/*
			sp.append("\nHi, developers -- ROBOT JOINED --\n[waveletID = " + 
					wavelet.getWaveletId() + "] \n" +
					"[waveID = " + waveID + "]\n");
			*/
			
			// set as default wave for users who do not have one
			String setForParticipants = "";
			boolean setdefaultfor = false;
			if (gadget != null) {

				// save the gadget instance
				//waveid_gadgetinstance.put(waveID, gadget);
				//waveid_blipinstance.put(waveID, rootBlip);
				//waveid_gadgettextview.put(waveID, rtextView);
				
				// see if we can actually get it's state:
				//sp.append("Gadget's state: [maxid = " + gadget.getField("_maxid") + "]\n");
				//gadget.setField("_maxid", Integer.toString(Integer.parseInt(gadget.getField("_maxid")) + 30));
				
			//	int index = reinsertGadgetBug(gadget, rtextView);
            //    gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
				//waveid_gadgetinstance.put(waveID, gadget);
				
				List<String> gadgetParticipants = rootBlip.getContributors();
				for (String participant : gadgetParticipants) {
					if (debugIsDeveloper(participant)) { // DEMO: only for developers
						
						// save for anyone without a default gadget wave:
						//users_waveid.put(participant, waveID);
						//if (!cache.containsKey(participant)) {
						if (getUser(participant) == null) {
							//cache.put(participant, waveID);
							makeUserDefaultWavePersistent(new User(participant, waveID));
							
							setForParticipants += participant + ", ";
							//log.warning("cache.put(participant, waveID); ["+participant+", "+waveID+"].");
							setdefaultfor = true;
						}
					}
				}

				if (setdefaultfor)
					sp.append("Saved this wave as a default ToDoIt wave for: [" + setForParticipants + "]\n");

			    // add a "set as default list" button
			    FormView formView = textView.getFormView();
			    formView.append(new FormElement(ElementType.BUTTON, ID_BUTT_SET_DEFAULT_WAVE, "Set as my default task list"));
			}
			
			
			sp.append( 
					"To update your default wave, add me to waves that have a " + 
					"ToDoIt gadget in the first Blip, with which the user interacted.\n" +
					"(To set your default wave, add me to it and add a Blip saying: " +
					"\"" + TEXT_USERREQ_SET_DEFAULT_WAVE+ "\".)");
			//log.warning(sp.toString()); // log.info() doesn't work?
	  	}

	    textView.append(sp.toString());
	}
	
	public void processFormButtonClicked(Event e) {
		String buttonName = e.getButtonName();
		
		if(buttonName.equals(ID_BUTT_SET_DEFAULT_WAVE)) {
		   	String waveID = e.getWavelet().getWaveId();
		   	String participant = e.getModifiedBy();
		   	
		   	// save as default wave for user
			makeUserDefaultWavePersistent(new User(participant, waveID));
			
			log.warning("Set waveID as default for partis: [" +waveID+ ", " +participant+ "]");
		}
	}
	
	public void processParticipantsChanged(RobotMessageBundle bundle, Event e) {
		// if gadget wave, add participants to task discussion waves
		Wavelet wavelet = bundle.getWavelet();
		
	    if (debugIsDeveloper(wavelet.getCreator())) {

	    	// Save Wavelet.rootBlip.Gadget(URL) as DEFAULT WAVE for users:
			String waveID = wavelet.getWaveId();
			Blip rootBlip = wavelet.getRootBlip();

			// find the Gadget
			Gadget gadget = null;
			TextView rtextView = rootBlip.getDocument();
			GadgetView rgadgetView = null;
			
			try {
				rgadgetView = rtextView.getGadgetView();
				gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
			}
			catch (Exception exc) {
				log.warning("getGadget/s(URL) exception instead of NULL? [" +
						exc.getMessage()+ "]");
			}
			
			if (gadget != null) {
				List<String> ps = new LinkedList<String>(e.getAddedParticipants());
				String waveletId = wavelet.getWaveletId();

				if (ps != null && !ps.isEmpty()) {
					Map<String, String> states = gadget.getProperties();
					Set<Entry<String, String>> entries = states.entrySet();
					
					for (Entry entry : entries) {
					  String key = (String) entry.getKey();

					  if (key.startsWith("wtask")) {			  
						  String value = (String) entry.getValue();
						  // if a discussion wave exists
						  if (value != null && !value.equals("create") && !value.equals("none")) {
							  int widix = value.indexOf("wave:");
							  if (widix > 0) value = value.substring(widix + 5);
							  
							  log.warning("adding joined partis to wl["+value+", "+waveletId+"]: " + ps);
							  
							  Wavelet wl = bundle.getWavelet(value, waveletId);
							  // add participants
							  ListIterator<String> iter = ps.listIterator();
							  while (iter.hasNext()) 
								  wl.addParticipant(iter.next());
						  }
					  }
					}
				}
			}
	    }
	}
	
	public void processBlipSubmitted(RobotMessageBundle bundle, Event e) {
		Wavelet ewavelet = e.getWavelet();
		String modifier = e.getModifiedBy();
		
		/*
		log.warning("processBlipSubmitted for modifier: [" + modifier + 
				"] on waveID: [" + ewavelet.getWaveId() + "] waveletID: [" +
				ewavelet.getWaveletId() + "].");
		*/
		
		if (!debugIsDeveloper(modifier /*ewavelet.getCreator()*/)) { // DEBUG: only work on dev waves
	    	return;
	    }
		
		// if user wants to set default wave:
		processSetDefaultWaveRequest(bundle, e);
		
		// get new task wave IDs from the write-back request
		processNewIds(bundle, e);
		
		// get task wave title changes and relay updates to gadget's wave
		processChangesTaskWave(bundle, e);
	}
	
	public void processDocumentChanged(RobotMessageBundle bundle, Event e) {
		Wavelet ewavelet = e.getWavelet();
		String modifier = e.getModifiedBy();
		
		if (!debugIsDeveloper(modifier /*ewavelet.getCreator()*/)) { // DEBUG: only work on dev waves
	    	return;
	    }
		// is a dev:
		
		String waveId = bundle.getWavelet().getWaveId();
		Blip eblip = e.getBlip();

		TextView ebTextView = eblip.getDocument();
		//String author = ebTextView.getAuthor();
		
		List<Annotation> requestAnnotations = null;
		try {
			requestAnnotations = ebTextView.getAnnotations(
				ANNOTATION_ADD_AS_TASK);
		} catch (Exception exc) {
			//...
		}
		
		if (requestAnnotations != null && !requestAnnotations.isEmpty()) {
			// process annotation
			Annotation ann = requestAnnotations.get(0);
			String annText = ebTextView.getText(ann.getRange());
			
			addTextAsTaskForUser(bundle, annText, modifier);
			
			// remove processed annotation
			ebTextView.deleteAnnotations(ann.getName());
		}
		
		// check requests for discussion wave creation from a task
		processCreateWaveTaskDiscussionRequest(bundle, e);
		
		// different approach: add saved tasks if this is a gadget wave
		//List<String> list = waveid_listoftaskstoadd.get(waveId);
		///List<String> list = (List<String>) cache.get("taskstoadd|"+waveId);
		List<List<String>> list = (List<List<String>>) cache.get("taskstoadd|"+waveId);
		
		//log.warning("list2 got: " + list2);
		
		// make title changes that we picked up on task waves
		//List<Tuple<String>> listoftitlechanges = waveid_listoftaskidtitle.get(waveId);
		List<List<String>> listoftitlechanges = (List<List<String>>) cache.get("taskstoedit|"+waveId);

		if ((list != null && !list.isEmpty()) || 
				(listoftitlechanges != null && !listoftitlechanges.isEmpty())) {
			//if (list != null) log.warning("list = [" + list.toString() + "]");
			//if (listoftitlechanges != null) log.warning("listoftitlechanges = [" + listoftitlechanges.toString() + "]");
			
			// find gadget:
			Gadget gadget = null;
			TextView rtextView = bundle.getWavelet().getRootBlip().getDocument();
			GadgetView rgadgetView = null;
			
		    boolean isRootBlip = e.getBlip().getBlipId().equals(
		            bundle.getWavelet().getRootBlipId());
		    
		    if (isRootBlip) { // the ONLY way this works apparently (the only way to get updated metadata)
				try {
					rgadgetView = rtextView.getGadgetView();
					gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
				}
				catch (Exception exc) {
					log.warning("getGadget/s(URL) exception1 instead of NULL? [" +
							exc.getMessage()+ "]");
				}
		    }
			
			if (gadget != null) {
				log.warning("got valid gadget!!!");
				
				if (list != null && !list.isEmpty()) {
					String jsDateStr = dateToJsStrDate(new Date());
			        
					///ListIterator<String> iter = list.listIterator();
					ListIterator<List<String>> iter = list.listIterator();
					while (iter.hasNext()) {
						List<String> tasktextadded = iter.next();
						String maxid = gadget.getField("_maxid");
						if (maxid == null) maxid = "1";
						String taskid = Integer.toString(Integer.parseInt(maxid) + 1);
						String taskuid = "task" + taskid + "_" + "ROBO";
						///String text = iter.next();
						String text = tasktextadded.get(0);
						String adder = tasktextadded.get(1);
						
						String taskObjJsonStringified = 
							"{\"id\":\"" + taskuid + "\"" +
							",\"title\":\"" + text + "\"" +
							",\"priority\":4" +
							",\"dueDate\":\"" + jsDateStr + "\",\"location\":\"\",\"tags\":[]" +
							",\"link\":null,\"progress\":0,\"status\":\"\"" +
							",\"creator\":\"" + adder + "\",\"owner\":\"\"" +
							",\"lastModifier\":\"todoit-reminder@appspot.com\"" +
							",\"lastModified\":\"" + jsDateStr + "\"" +
							",\"completedDate\":null,\"completer\":\"\"" +
							",\"addedDate\":\"" + jsDateStr + "\",\"freq\":\"\"" +
							",\"dur\":\"\"}";
						
						gadget.setField(taskuid, taskObjJsonStringified);
						gadget.setField("_maxid", taskid);
						gadget.setField("v"+taskuid, "1");
					}
					
					cache.remove("taskstoadd|"+waveId);
				}
				
				
				// update according to title changes in task waves
				if (listoftitlechanges != null && !listoftitlechanges.isEmpty()) {
					ListIterator<List<String>> iter = listoftitlechanges.listIterator();
					while (iter.hasNext()) {
						List<String> taskidtitle = iter.next();
						String taskuid = taskidtitle.get(0);
						String title = taskidtitle.get(1);
						String taskObjJsonString = gadget.getField(taskuid);
						log.warning("taskidtitle changes: taskid: [" + taskuid + "] new title: [" + title + "].");
						
						if (taskObjJsonString != null && taskObjJsonString.length() > 10) {
							String fieldStart = "title\":\"";
							String fieldEnd = "\",";
							int len = fieldStart.length();
							int beginIndex = taskObjJsonString.indexOf(fieldStart);
							String pre = taskObjJsonString.substring(0, beginIndex + len);
							int endIndex = taskObjJsonString.indexOf(fieldEnd, beginIndex + len);
							String post = taskObjJsonString.substring(endIndex);
							
							String newTaskObj = pre + title + post;
							
							String ver = Integer.toString(Integer.parseInt((gadget.getField("v"+taskuid))) + 1);
							gadget.setField(taskuid, newTaskObj);
							gadget.setField("v"+taskuid, ver);
							
						}
						
						cache.remove("taskstoedit|"+waveId);
					}
				}
				
				int index = reinsertGadgetBug(gadget, rtextView);
	            try {
		            rgadgetView = rtextView.getGadgetView();
		            gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
					//waveid_gadgetinstance.put(waveId, gadget);
	            } catch (Exception exc){
	            	log.warning("Failed to fix gadget update bug.");
	            }
			}
		}


	    /*
		log.warning( // Log each event:
			"processDocumentChanged: \nblipId: [" + eblip.getBlipId() +
			"]\n (text: " + ebTextView.getText(new Range(0, 30)) + " ...)" +
			//"\n Blip Author: " + author +  // == null on changes?
			"\n Event's ModifiedBy: " + modifier +
			//"\n WaveletId: " + ewavelet.getWaveletId() + // == "conv/root"?
			"\n WaveId: " + ewavelet.getWaveId() +
			"\n Related(todoit) Annotations: " + requestAnnotations
		); 
		*/
	}

	public static String dateToJsStrDate(Date date) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.00Z'");
		java.util.Calendar cal = Calendar.getInstance(new SimpleTimeZone(0, "GMT"));
		sdf.setCalendar(cal);
		String jsDateStr = sdf.format( date );
		return jsDateStr;
	}
	
	/**
	 * Read new IDs in this wave which come from a write-back request to save new task waves' IDs
	 */
	public void processChangesTaskWave(RobotMessageBundle bundle, Event e) {
		Wavelet wavelet = bundle.getWavelet();
		
	    boolean isRootBlip = e.getBlip().getBlipId().equals(
	            bundle.getWavelet().getRootBlipId());
	    
	    if (isRootBlip) {
	    	if (wavelet.hasDataDocument("todoit-gadget-waveid") && 
	    			wavelet.hasDataDocument("todoit-gadget-taskid")) {
	    		String waveId = wavelet.getDataDocument("todoit-gadget-waveid");
	    		String taskId = wavelet.getDataDocument("todoit-gadget-taskid");
	    		// get new title
	    		String title = wavelet.getTitle();
	    		
	    		log.warning("got taskidtitle change: taskid: [" + taskId + "] new title: [" + title + "].");
	    		
	    		// save (taskid, title)
	    		///Tuple<String> taskidtitle = new Tuple<String>(taskId, title);
	    		List<String> taskidtitle2 = new ArrayList<String>();
	    		taskidtitle2.add(taskId);
	    		taskidtitle2.add(title);
	    			    		
	    		//List<Tuple<String>> list = waveid_listoftaskidtitle.get(waveId);
	    		List<List<String>> list2 = (List<List<String>>) cache.get("taskstoedit|"+waveId);
	    		
	    		//if (list == null) list = new LinkedList<Tuple<String>>();
	    		if (list2 == null) list2 = new LinkedList<List<String>>();
	    		
	    		//list.add(taskidtitle);
	    		list2.add(taskidtitle2);
	    		
	    		log.warning("listtoedit2: " + list2);
	    		
	    		//waveid_listoftaskidtitle.put(waveId, list);
	    		cache.put("taskstoedit|"+waveId, list2);
	    	}
	    }
	}
	
	/**
	 * Read new IDs in this wave which come from a write-back request to save new task waves' IDs
	 */
	public void processNewIds(RobotMessageBundle bundle, Event e) {
		Wavelet wavelet = bundle.getWavelet();
		
		// find gadget:
		Gadget gadget = null;
		TextView rtextView = bundle.getWavelet().getRootBlip().getDocument();
		GadgetView rgadgetView = null;
		boolean madeGadgetUpdates = false; 
		
	    boolean isRootBlip = e.getBlip().getBlipId().equals(
	            bundle.getWavelet().getRootBlipId());
	    
	    if (isRootBlip) { // the ONLY way this works apparently (the only way to get updated metadata)
			try {
				rgadgetView = rtextView.getGadgetView();
				gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
			}
			catch (Exception exc) {
				//log.warning("getGadget/s(URL) exception2 instead of NULL? [" +	exc.getMessage()+ "]");
			}
	    }
	
		// From the Wave API FAQ: http://wave-api-faq.appspot.com/#robotgadgetstates
		// Once you have a handle to a gadget you can iterate through all the gadget state's keys.

		if (gadget != null) {

			// go over all _new_ids_ and update these IDs in the gadget state
			if (wavelet.hasDataDocument("_new_ids_")) {
				String new_ids = wavelet.getDataDocument("_new_ids_");
				
				if (new_ids.length() > 10) {
					log.warning("Found _new_ids_: [" + new_ids + "].");
					String[] taskIds = new_ids.split("todoit-waveid-for-");

					for (int i = 0; i < taskIds.length; i++ ) {
						// wave save in an annoying format:
						//    "todoit-waveid-for-TASKID:WAVEID WAVELETID BLIPID"
						//    we need TASKID and WAVEID:
						
						log.warning("taskIds["+i+"]: [" + taskIds[i] + "]");
						
						String[] temp = taskIds[i].split(":");
						if (temp.length < 2) continue;
						
						String taskId = temp[0];
						String taskWaveId = temp[1].split(" ")[0];
						
						log.warning("taskId: [" + taskId + "] taskWaveId: [" + 
								taskWaveId +"] link: [" + 
								TEXT_LINK_TO_WAVEID_SB + taskWaveId+ "].");
						
						// save updated ID to gadget state
						gadget.setField("w"+taskId, TEXT_LINK_TO_WAVEID_SB + taskWaveId);
						madeGadgetUpdates = true;
					}
										
				}
				
				// clear _new_ids_ data doc until next time
				wavelet.setDataDocument("_new_ids_", "");
			}

			if (madeGadgetUpdates) reinsertGadgetBug(gadget, rtextView);

		}
		
	}

	
	/**
	 * Sets the current wave as the default target wave for the user who said
	 * TEXT_USERREQ_SET_DEFAULT_WAVE in a Blip on this wave that created this event.
	 */
	public void processSetDefaultWaveRequest(RobotMessageBundle bundle, Event e) {
		String strBlip = e.getBlip().getDocument().getText();
	    String delimiter = " ";
	    String temp = strBlip.split(delimiter)[0].trim();

		//log.warning("processSetDefaultWaveRequest text: [" + temp + "].");
		
	    if (temp.equals(TEXT_USERREQ_SET_DEFAULT_WAVE)|| 
	    	temp == TEXT_USERREQ_SET_DEFAULT_WAVE) {

			Wavelet wavelet = e.getWavelet();
	    	String waveID = wavelet.getWaveId();
	    	String participant = e.getModifiedBy();
	    	
	    	// save as default wave for user
	    	//users_waveid.put(participant, waveID);
			//cache.put(participant, waveID);
			//log.warning("cache.put(participant, waveID); ["+participant+", "+waveID+"].");
	    	
			makeUserDefaultWavePersistent(new User(participant, waveID));
			log.warning("stored User(id).defwaveid = " + getUser(participant).getDefaultWaveId());
			
			Blip blip = e.getBlip();
		    TextView atextView = blip.getDocument();
		    String sMsg = "ToDoIt Robot: saved this wave as default wave" + 
				" for participant: [" + participant + "].";
		    atextView.append("\n   --- " + sMsg);
		    log.warning(sMsg);
	    }
		
	}

	/**
	 * Make user persistent (save in DB)
	 * @param user
	 */
	public static void makeUserDefaultWavePersistent(User user) {
	    PersistenceManager pm = PMF.get().getPersistenceManager();
	    try {
	        pm.makePersistent(user);
	    } finally {
	        pm.close();
	    }
	}
	
	/**
	 * Get user from DB
	 * @param participantId the user's participant ID
	 * @return the user if in DB, or null otherwise
	 */
	public static User getUser(String participantId) {
	    PersistenceManager pm = PMF.get().getPersistenceManager();
	    User u, detached = null;
	    try {
	    	u = pm.getObjectById(User.class, participantId);
	    	detached = pm.detachCopy(u);
	    } catch (Exception exc) {
	    	//log.warning("not in db");
	    	detached = null;
	    } finally {
	        pm.close();
	    }
	    return detached;
	}
	
	
	/**
	 * Adds text as a task inside the default gadget wave of userid. 
	 */
	public void addTextAsTaskForUser(RobotMessageBundle bundle,
			String text, String userid) {

		String sOtherInfo = "";
		
		/* GET Wavelet INSTANCE BY ID !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		 * The "Magic Call": bundle.getWavelet(String waveId, String waveletId); !!!
		 * NOTE: it is currently not supported to get a Wavelet's metadata by ID !!!
		 *  - When Wave API is updated, this code will be usable.
		 * SEE LONG NOTE AT TOP OF FILE. */
		
		/*
		 // This all fails because of the above NOTE.
		// SANDBOX ID
		String waveletId = "wavesandbox.com!conv+root";
		String waveId = users_waveid.get(userid);
		
		if (waveId != null) {
			Wavelet wavelet = bundle.getWavelet(waveId, waveletId);
			
			Blip rblip =  wavelet.getRootBlip();
			TextView textView = rblip.getDocument();
			GadgetView gadgetView = textView.getGadgetView();
			
			log.warning("gadgetView: -" +gadgetView+ "-\n textView: -" +textView+ "-\n "+
					"tv.Elements: " +textView.getElements()+ "\n " +
					"wl.RootBlipId: " +wavelet.getRootBlipId() + "\n " +
					"wl.DataDocs: " +wavelet.getDataDocuments() + "\n " +
					"rblip: -" + rblip.toString() + "\n " +
					"wl: -" + wavelet.toString() + "\n "
					);
					
			try { // http://code.google.com/p/google-wave-resources/issues/detail?id=181
				sOtherInfo += "\nGadgets found: " + gadgetView.getGadgets();
				Gadget gadget = gadgetView.getGadget(TODOIT_GADGET_URL);
				if (gadget != null) {
					// operate on gadget's state (keys, values from gadget)
					String maxid = gadget.getField("_maxid");
					
					sOtherInfo += "Gadget: " +gadget+ 
						"\n---state:\n<_maxid," +maxid+ ">";
				}
			}
			catch (Exception e) {
				log.warning("getGadget/s(URL) exception instead of NULL? [" +
						e.getMessage()+ "]");
			}
		}
		 */
		
		
		/*
		Gadget gadget = null;
		if (waveId != null) {
			gadget = waveid_gadgetinstance.get(waveId);
		}

		if (gadget != null) {
			// see if we can actually get it's state:
			String maxid = gadget.getField("_maxid");
			sOtherInfo += "Found Gadget's state: [maxid = " + maxid + "]\n";
			
			// add text as task in gadget state
		
			String taskid = Integer.toString(Integer.parseInt(maxid) + 1);
			String taskuid = "task" + taskid + "_" + "ROBO";
			
			String taskObjJsonStringified = 
				"{\"id\":\"" + taskuid + "\"" +
				",\"title\":\"" + text + "\"" +
				",\"priority\":4" +
				",\"dueDate\":null,\"location\":\"\",\"tags\":[]" +
				",\"link\":null,\"progress\":0,\"status\":\"\"" +
				",\"creator\":\"" + userid + "\",\"owner\":\"\"" +
				",\"lastModifier\":\"\",\"lastModified\":null" +
				",\"completedDate\":null,\"completer\":\"\"" +
				",\"addedDate\":null,\"freq\":\"\"" +
				",\"dur\":\"\"}";
			
			gadget.setField(taskuid, taskObjJsonStringified);
			gadget.setField("_maxid", taskid);
			gadget.setField("v"+taskuid, "1");
			
			String testv = gadget.getField("vtask1_xgcr"); // test if we pick up Gadget state updates on the saved GadgetInstance
			                                               // ^ crap. apparently we don't. This may break state updates that real users make on gadget.

			Blip rblp = waveid_blipinstance.get(waveId);
			//TextView rtextView = waveid_gadgettextview.get(waveId);
			TextView rtextView = rblp.getDocument();
			int index = reinsertGadgetBug(gadget, rtextView);
            try {
	            GadgetView rgadgetView = rtextView.getGadgetView();
	            gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
				waveid_gadgetinstance.put(waveId, gadget);
				int ix = reinsertGadgetBug(gadget, rtextView);
            } catch (Exception e){
            	log.warning("Failed to fix gadget update bug.");
            }
		}
		*/
		// END of the above NOTE.

		String waveId = null;
		//String waveId = users_waveid.get(userid);
		//String waveId = (String) cache.get(userid);

		User user = getUser(userid);
		String waveId2;
		if (user != null) waveId2 = user.getDefaultWaveId();
		else waveId2 = null;
		log.warning("getUser(id) = ["+userid+", got: "+waveId2+"].");
		waveId = waveId2;
		
		//log.warning("getUser(id); ["+userid+", got: "+waveId+"].");

		
		if (waveId == null) {
			// post a new Blip, to inform users...
			sOtherInfo += " User doesn't have a default wave set that I know of. " + 
				"User should remove and re-add me to waves that have a " +
				"ToDoIt gadget in the first Blip, with which the user interacted.\n" +
				"(To set your default wave, add me to it and add a Blip saying: " +
				"\"" + TEXT_USERREQ_SET_DEFAULT_WAVE+ "\".)";
			Wavelet wavelet = bundle.getWavelet();
			Blip blip = wavelet.appendBlip();
		    TextView atextView = blip.getDocument();
		    atextView.append(//"This blip resulted from: \n" + 
					//"addTextAsTaskForUser: \nText: [" + text +
					//"]\n For userid: [" + userid + "]" +
					sOtherInfo
			);
		}

		
		if (waveId != null) {
			
	        // different approach: temp save tasks in memory until gadget event:
	    	//List<String> list = waveid_listoftaskstoadd.get(waveId);
	    	///List<String> list2 = (List<String>) cache.get("taskstoadd|"+waveId);
	    	List<List<String>> list2 = (List<List<String>>) cache.get("taskstoadd|"+waveId);
	    	
	    	//if (list == null) list = new LinkedList<String>();
	    	///if (list2 == null) list2 = new LinkedList<String>();
	    	if (list2 == null) list2 = new LinkedList<List<String>>();

	    	List<String> tasktextadder = new ArrayList<String>();
	    	tasktextadder.add(text);
	    	tasktextadder.add(userid);
	    	
	    	// unique to avoid bug on Wave Preview that doesn't clear annotations
	    	boolean in = false;
			ListIterator<List<String>> iter = list2.listIterator();
			while (iter.hasNext()) {
				if (text.equals(iter.next().get(0))) {
					in = true;
					break;
				}
			}

	    	if (!in) 
	    		///list2.add(text);
	    		list2.add(tasktextadder);
	    	
	    	log.warning("list2: " + list2);
	    	
	    	//waveid_listoftaskstoadd.put(waveId, list);
	    	cache.put("taskstoadd|"+waveId, list2);
	    	
			sOtherInfo += //"Adding as task to user's default gadget\n" +
				//"[Testval=" +testv+ "][index=" +index+ "] \n" +
				TEXT_CONTEXT_LOST_MSG;			
		}
		
		
		/* 
		 // Try to fix context by generating a new event on the gadget:
		// post a new Blip to inform users...
		if (waveId != null) {
			String rootWaveletID = bundle.getWavelet().getWaveletId();
			Wavelet wavelet = bundle.getWavelet(waveId, rootWaveletID);
			Blip blip = wavelet.appendBlip();
		    TextView atextView = blip.getDocument();
		    atextView.append("This blip resulted from: \n" + 
					"addTextAsTaskForUser: \nText: [" + text +
					"]\n For userid: [" + userid + "]" +
					"\n User's (and this wave's) waveId: [" + waveId + "]\n" +
					sOtherInfo
			);
		    
		    // try to generate event related to gadget's context:
		    try {
			    Blip rblip =  wavelet.getRootBlip();
				TextView rtextView = rblip.getDocument();
				rtextView.append("[try to generate event]");
				log.warning("[try to generate event]");
            } catch (Exception exc){
            	log.warning("Failed to generate event related to gadget's context.");
            }
		}
		*/
		
		log.warning(
				"addTextAsTaskForUser: \nText: [" + text +
				"]\n For userid: [" + userid + "]" +
				"\n User's (and this wave's) waveId: [" + waveId + "]\n" //+
				//sOtherInfo
		);
	}

	/**
	 * Create a new discussion wave for a task if a request exists in this event.
	 * Operates on a gadget event which requests creation of wave to Discuss a specific task.
	 */
	public void processCreateWaveTaskDiscussionRequest(RobotMessageBundle bundle, Event e) {
		// find gadget:
		Gadget gadget = null;
		TextView rtextView = bundle.getWavelet().getRootBlip().getDocument();
		GadgetView rgadgetView = null;
		boolean madeGadgetUpdates = false; 
		
		boolean isRootBlip = false;
		
		try {
			isRootBlip = e.getBlip().getBlipId().equals(
					bundle.getWavelet().getRootBlipId());
		} catch (Exception exc) {
			//
		}
	    
	    if (isRootBlip) { // the ONLY way this works apparently (the only way to get updated metadata)
			try {
				rgadgetView = rtextView.getGadgetView();
				gadget = rgadgetView.getGadget(TODOIT_GADGET_URL);
			}
			catch (Exception exc) {
				//log.warning("getGadget/s(URL) exception2 instead of NULL? [" +	exc.getMessage()+ "]");
			}
	    }
	
		// From the Wave API FAQ: http://wave-api-faq.appspot.com/#robotgadgetstates
		// Once you have a handle to a gadget you can iterate through all the gadget state's keys.

		if (gadget != null) {
			String waveId = bundle.getWavelet().getWaveId();
			Map<String, String> states = gadget.getProperties();
			Set<Entry<String, String>> entries = states.entrySet();

			for (Entry entry : entries) {
			  String key = (String) entry.getKey();
			  
			  
			  if (key.startsWith("wtask")) {
				  String participant = e.getModifiedBy();				  
				  String value = (String) entry.getValue();
				  
				  if (value.equals("create")) {
					  log.warning("Got a 'create' request for a task discussion wave." +
							  "\nParticipant = [" + participant + "]"+
							  "\nTask id = [" + key + "]");
					  
					  //List<String> gadgetParticipants = e.getBlip().getContributors();
					  List<String> wlParticipants = e.getWavelet().getParticipants();
					  List<String> p = new ArrayList<String>(wlParticipants);
					  p.add(participant);
					  //p.add("public@a.googlewave.com"); // make this a universally public wave?
					  //p.add("public@a.wavesandbox.com");
					  //p.add("public@a.gwave.com");
					  					  
					  
					  String taskuid = key.substring(1); // get task's ID from gadget's state
					  String taskObjJsonString = gadget.getField(taskuid); // get task object
					  
					  // get task's title
					  String title = getTaskObjJsonStringField(taskObjJsonString, "title\":\"", "\",");

					  // create a new wavelet:
					  // FIXME: bug in Wave API:  http://code.google.com/p/google-wave-resources/issues/detail?id=172
					  
					  // Test one -- seems to fail to write-back the ID of new wave
					  //Wavelet wl2 = bundle.createWavelet(p);
					  //wl2.addParticipant(participant);
					  
					  // Test two
					  Wavelet wl = bundle.getWavelet();
					  // request that the new wave's ID be written to a data doc on this wavelet
					  Wavelet wl2 = wl.createWavelet(p, "todoit-waveid-for-" + taskuid);
					  
					  wl2.setTitle(/*"ToDoIt discussion wave for task: " +*/ title);
					  wl2.setDataDocument("todoit-gadget-waveid", waveId); // save reference to waveID of gadget's
					  wl2.setDataDocument("todoit-gadget-taskid", taskuid); // save reference to taskID of task
					  String newWaveId = wl2.getWaveId();
					  
					  log.warning("Created new wave, wl IDs -- [" + newWaveId + "][" +
							  wl2.getWaveletId() + "]" +
					  		  "\nData docs of new wl: " +
							  wl2.getDataDocuments()
							  /* + "\nData docs of current wl: " + 
							  bundle.getWavelet().getDataDocuments()*/
							  );
					  
					  // save discussion wave reference in gadget state
					  gadget.setField(key, TEXT_LINK_TO_WAVEID_SB + newWaveId + ".1");
					  madeGadgetUpdates = true;
					  
					  // post new Blip on new wave
					  Blip newBlip = wl2.appendBlip();
					  TextView newBlipTV = newBlip.getDocument();
					  newBlipTV.append("(this is a ToDoIt task discussion wave)\n"
							  // + " for task ID: [" +
							  //taskuid + "] from gadget at wave ID: [" + waveId +"].\n" +
							  //TEXT_CONTEXT_LOST_MSG
							  );

					  // add a link back to origin wave
					  int lowerIndex = newBlipTV.getText().length();
					  newBlipTV.append("Related task list wave >>");
					  int upperIndex = newBlipTV.getText().length();
					  newBlipTV.setAnnotation(new Range(lowerIndex, upperIndex), "link/wave", waveId);
					  
					  // post some info on the task
					  String tags = getTaskObjJsonStringField(taskObjJsonString, "tags\":[", "],");
					  String duedate = getTaskObjJsonStringField(taskObjJsonString, "dueDate\":\"", "\",");
					  /*
					  Blip newBlip2 = wl2.appendBlip();
					  TextView newBlip2TV = newBlip2.getDocument();
					  newBlip2TV.append("This task's Tags: [" +
							  tags + "]\nThis task's Due Date [" + duedate +"].\n"
							  );
					   */
				  }
			  }
			}

			if (madeGadgetUpdates) reinsertGadgetBug(gadget, rtextView);
		}
	}

	/**
	 * Returns the value of field of name fieldStart in task object JSON string. 
	 * @param taskObjJsonString
	 * @param fieldStart field name plus terminator string (e.g. "title\":\"" -- title field).
	 * @param fieldEnd field value's termination string (e.g. "\"," -- a separator).
	 * @return String value of this field
	 */
	private String getTaskObjJsonStringField(String taskObjJsonString, String fieldStart, String fieldEnd) {
		  int len = fieldStart.length();
		  int beginIndex = taskObjJsonString.indexOf(fieldStart);
		  int endIndex = taskObjJsonString.indexOf(fieldEnd, beginIndex + len);
		  String title = taskObjJsonString.substring(beginIndex + len, endIndex);
		return title;
	}
	
	/** FIXME: try to circumvent bug: http://development.lombardi.com/?p=882
	 * by removing and re-inserting the gadget in the Blip.
	 * This forces Wave to reload the updated gadget state.
	 * This also has the ill-effect of redrawing, and even showing 2 copies of the gadget.
	 * @param gadget
	 * @param rtextView of Blip containing the gadget
	 * @return index of gadget in the TextView
	 */
	private int reinsertGadgetBug(Gadget gadget, TextView textView) {
		int index = textView.getPosition(gadget);
		if (index >= 0) {
			textView.deleteElement(index);
			textView.insertElement(index, gadget);
		}
		return index;
	}
	
	/**
	 * Returns a URL address to waveId.
	 * TODO: any less stupid way to generate proper links to waves?
	 */
	public static String makeWaveIdUrl(String waveId) {
		String url = "";
		int gwix = waveId.indexOf("googlewave");
		int sbix = waveId.indexOf("sandbox");
		if (gwix > 0) {
			url = "https://wave.google.com/wave/#restored:wave:";
		} else if (sbix > 0) {
			url = "https://wave.google.com/a/wavesandbox.com/#restored:wave:";
		}
		String suf = waveId.replace("+", "%252B");
		return (url + suf);
	}
	
	/**
	 * DEBUG: method for development. Tests if userid is a developer's user ID.
	 * @return true iff userid is a developer
	 */
	public boolean debugIsDeveloper(String userid) {
		return true;
		/*
		return ("3range@googlewave.com".equals(userid) ||
				"eyalw@wavesandbox.com".equals(userid) ||
				"eyalw@googlewave.com".equals(userid) ||
				"eyalwiener@wavesandbox.com".equals(userid) ||
				"eyalwiener@googlewave.com".equals(userid) ||
			  "3range@wavesandbox.com".equals(userid) ||
			  "3range-test@wavesandbox.com".equals(userid));
		*/
	}
}


/* 
 // Code for HTTP servlet to handle XHR for the "create a new wave from task" feature.
package todoitreminder;

import java.io.IOException;
import javax.servlet.http.*;


@SuppressWarnings("serial")
public class Todoit_reminderServlet extends HttpServlet {
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		resp.setContentType("text/plain");
		resp.getWriter().println("Hello, world");
	}
}

*/

/*
 // How to add a new Gadget to a Blip:
GadgetView gadgets = textView.getGadgetView();
Gadget gadget = new Gadget("http://embeddy.appspot.com/gadget/content.xml");
gadgets.append(gadget);
gadget.setField("waveid", "'" + wavelet.getWaveId() + "'");
*/
